//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Media;

namespace OpenLS.Spreadsheet.UI
{
    public class GridLineControl : FrameworkElement
    {
        public GridLineControl()
        {
            pen = new Pen(new SolidColorBrush(Color.FromArgb(255, 208, 215, 229)), 1);
            pen.Freeze();
        }
        // Using a DependencyProperty as the backing store for Worksheet.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WorksheetFragmentProperty =
            DependencyProperty.Register("WorksheetFragment", typeof (IWorksheetFragment), typeof (GridLineControl),
                                        new FrameworkPropertyMetadata(null, OnWorksheetFragmentChanged));

        public static readonly DependencyProperty LineBrushProperty =
            DependencyProperty.Register("LineBrush", typeof (Brush), typeof (GridLineControl),
                                        new FrameworkPropertyMetadata(
                                            new SolidColorBrush(Color.FromArgb(255, 208, 215, 229)),
                                            FrameworkPropertyMetadataOptions.AffectsRender, OnLineBrushChanged));

        private Pen pen;

        public IWorksheetFragment WorksheetFragment
        {
            get { return (IWorksheetFragment) GetValue(WorksheetFragmentProperty); }
            set { SetValue(WorksheetFragmentProperty, value); }
        }

        /// <summary>
        /// Get or set the brush used to draw lines.
        /// </summary>
        public Brush LineBrush
        {
            get { return (Brush) GetValue(LineBrushProperty); }
            set { SetValue(LineBrushProperty, value); }
        }

        private static void OnWorksheetFragmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var This = (GridLineControl) d;
            var old = (IWorksheetFragment) e.OldValue;
            var newVp = (IWorksheetFragment) e.NewValue;

            if (old != null)
            {
                Worksheet oldW = old.Worksheet;
                oldW.LayoutChanged -= This.Worksheet_LayoutChanged;
                oldW.Columns.LayoutChanged -= This.Worksheet_LayoutChanged;
                oldW.Rows.LayoutChanged -= This.Worksheet_LayoutChanged;
                oldW.PropertyChanged -= This.newV_PropertyChanged;
                oldW.View.PropertyChanged -= This.newV_PropertyChanged;
                oldW.CollectionChanged -= This.Worksheet_LayoutChanged;
                old.PropertyChanged -= This.PartialView_PropertyChanged;
            }
            if (newVp != null)
            {
                Worksheet newV = newVp.Worksheet;
                newV.LayoutChanged += This.Worksheet_LayoutChanged;
                newV.Columns.LayoutChanged += This.Worksheet_LayoutChanged;
                newV.Rows.LayoutChanged += This.Worksheet_LayoutChanged;
                newV.PropertyChanged += This.newV_PropertyChanged;
                newV.View.PropertyChanged += This.newV_PropertyChanged;
                newV.CollectionChanged += This.Worksheet_LayoutChanged;
                newVp.PropertyChanged += This.PartialView_PropertyChanged;
            }
            This.InvalidateVisual();
        }

        private void PartialView_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            InvalidateVisual();
        }

        private void newV_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case SheetView.GridLinesVisiblePropertyName:
                case "Top":
                case "Left":
                    InvalidateVisual();
                    break;
            }
            //throw new NotImplementedException(); 
            //if (e.PropertyName == WorksheetView.GridLinesVisiblePropertyName || e.PropertyName == WorksheetView.TopLeftPropertyName)
            //  this.InvalidateVisual();
        }

        private void Worksheet_LayoutChanged(object sender, EventArgs e)
        {
            InvalidateVisual();
        }


        //\\ Pen p = new Pen(new SolidColorBrush(Color.FromArgb(255, 208, 215, 229)), 0.5);
        //\\#FFD0D7E5

        private static void OnLineBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var newValue = (Brush) e.NewValue;
            var c = (GridLineControl) d;
            c.pen = new Pen(newValue, 1);
            c.pen.Freeze();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            IWorksheetFragment v = WorksheetFragment;
            if (v == null)
                return;
            var worksheet = v.Worksheet;
            var view = worksheet.View;
            if (view != null)
            {
                if (!view.GridLinesVisible)
                    return;
                if (SpreadsheetElement.GetColorMode(this) == ColorMode.Color && view.ZoomScale <= 40)
                    return;
            }
            var verticalBreaks = new Dictionary<int, DimensionSegmentList>();
            var horizontalBreaks = new Dictionary<int, DimensionSegmentList>();
            foreach (Cell c in v.GetCells())
            {
                if (c.HasSpan)
                {
                    AddBreaks(new DimensionSegment(c.Column.Index, c.Column.Index + c.ColumnSpan), c.Row.Index + 1,
                              c.Row.Index + c.RowSpan - 1, horizontalBreaks);
                    AddBreaks(new DimensionSegment(c.Row.Index, c.Row.Index + c.RowSpan), c.Column.Index + 1,
                              c.Column.Index + c.ColumnSpan - 1, verticalBreaks);
                }
                Borders borders = c.ActualBorders;
                if (borders != null)
                {
                    if (BorderDrawer.HasBorder(borders.Top))
                        AddBreaks(new DimensionSegment(c.Column.Index, c.Column.Index + c.ColumnSpan), c.Row.Index,
                                  c.Row.Index, horizontalBreaks);
                    if (BorderDrawer.HasBorder(borders.Bottom))
                        AddBreaks(new DimensionSegment(c.Column.Index, c.Column.Index + c.ColumnSpan),
                                  c.Row.Index + c.RowSpan, c.Row.Index + c.RowSpan, horizontalBreaks);
                    if (BorderDrawer.HasBorder(borders.Left))
                        AddBreaks(new DimensionSegment(c.Row.Index, c.Row.Index + c.RowSpan), c.Column.Index,
                                  c.Column.Index, verticalBreaks);
                    if (BorderDrawer.HasBorder(borders.Right))
                        AddBreaks(new DimensionSegment(c.Row.Index, c.Row.Index + c.RowSpan),
                                  c.Column.Index + c.ColumnSpan, c.Column.Index + c.ColumnSpan, verticalBreaks);
                }
            }
            foreach (DimensionSegmentList list in horizontalBreaks.Values)
            {
                list.CleanUp();
            }
            foreach (DimensionSegmentList list in verticalBreaks.Values)
            {
                list.CleanUp();
            }
            {
                int column = WorksheetFragment.Left;
                double sumHeight = Enumerable.Sum(WorksheetFragment.GetRows(), r => r.ActualHeight);
                double sumWidth = Enumerable.Sum(WorksheetFragment.GetColumns(), r => r.ActualWidth);
                double maxX = Math.Min(ActualWidth, sumWidth);
                double maxY = Math.Min(ActualHeight, sumHeight);
                var size = new Size(maxX, maxY);
                {
                    ColumnCollection columns = WorksheetFragment.Worksheet.Columns;
                    var xs = new HashList<double>();
                    for (double x = 0;
                         x <= size.Width && columns.ValidIndex(column);
                         x += columns[column].ActualWidth, column++)
                    {
                        DimensionSegmentList l;
                        if (!verticalBreaks.TryGetValue(column, out l))
                        {
                            if (!xs.Contains(x))
                            {
                                DrawLine(drawingContext, new Point(x, 0), new Point(x, size.Height));
                                xs.Add(x);
                            }
                        }
                        else
                        {
                            double y = 0;
                            foreach (DimensionSegment c in l)
                            {
                                double y1 = RowIndexToY(c.LowIndex);
                                double y2 = RowIndexToY(c.HighIndex);
                                DrawLine(drawingContext, new Point(x, y), new Point(x, y1));
                                y = y2;
                            }
                            DrawLine(drawingContext, new Point(x, y), new Point(x, size.Height));
                        }
                    }
                }
                int row = WorksheetFragment.Top;
                var ys = new HashList<double>();

                for (double y = 0; y <= size.Height; y += WorksheetFragment.Worksheet.Rows[row].ActualHeight, row++)
                {
                    DimensionSegmentList l;
                    if (!horizontalBreaks.TryGetValue(row, out l))
                    {
                        if (!ys.Contains(y))
                        {
                            DrawLine(drawingContext, new Point(0, y), new Point(size.Width, y));
                            ys.Add(y);
                        }
                    }
                    else
                    {
                        double x = 0;
                        foreach (DimensionSegment c in l)
                        {
                            double lowX = ColumnIndexToX(c.LowIndex);
                            double highX = ColumnIndexToX(c.HighIndex);
                            DrawLine(drawingContext, new Point(x, y), new Point(lowX, y));
                            x = highX;
                        }
                        DrawLine(drawingContext, new Point(x, y), new Point(size.Width, y));
                    }
                    if (row >= Worksheet.MaxRowIndex)
                        break;
                }
            }
        }

        private static void AddBreaks(DimensionSegment c, int minIndex, int maxIndex,
                                      IDictionary<int, DimensionSegmentList> breaks)
        {
            for (int row = minIndex; row <= maxIndex; row++)
            {
                AddBreak(c, row, breaks);
            }
        }

        private static void AddBreak(DimensionSegment c, int index, IDictionary<int, DimensionSegmentList> breaks)
        {
            DimensionSegmentList l;
            if (!breaks.TryGetValue(index, out l))
            {
                l = new DimensionSegmentList();
                breaks.Add(index, l);
            }
            l.Add(c);
        }

        private void DrawLine(DrawingContext dc, Point pt1, Point pt2)
        {
            Contract.Assert(pen != null);
            Contract.Assert(pen.IsFrozen);
            //\\ Contract.Assert(pen.Brush == LineBrush);

            dc.DrawLine(pen, new Point((int) pt1.X + 0.5, (int) pt1.Y + 0.5),
                        new Point((int) pt2.X + 0.5, (int) pt2.Y + 0.5));
        }

        private double ColumnIndexToX(int column)
        {
            ColumnCollection columns = WorksheetFragment.Worksheet.Columns;
            return columns[column].Offset - columns[GetLeft(WorksheetFragment)].Offset;
        }

        private double RowIndexToY(int row)
        {
            RowCollection rows = WorksheetFragment.Worksheet.Rows;
            return rows[row].Offset - rows[GetTop(WorksheetFragment)].Offset;
        }

        private static int GetTop(IWorksheetFragment ws)
        {
            return ws.Top;
        }

        private static int GetLeft(IWorksheetFragment ws)
        {
            return ws.Left;
        }

        #region Nested type: DimensionSegment

        private struct DimensionSegment
        {
            public readonly int LowIndex;
            public int HighIndex;

            public DimensionSegment(int low, int high)
            {
                Contract.Assert(low < high);
                LowIndex = low;
                HighIndex = high;
            }
        }

        #endregion

        #region Nested type: DimensionSegmentList

        private class DimensionSegmentList : List<DimensionSegment>
        {
            public void CleanUp()
            {
                Sort((c1, c2) => c1.LowIndex.CompareTo(c2.LowIndex));
                int i = 0;
                while (i < Count - 1)
                {
                    DimensionSegment previous = this[i];
                    DimensionSegment next = this[i + 1];
                    if (next.LowIndex <= previous.HighIndex)
                    {
                        previous.HighIndex = Math.Max(previous.HighIndex, next.HighIndex);
                        this[i] = previous;
                        RemoveAt(i + 1);
                        continue;
                    }

                    i++;
                }
            }
        }

        #endregion
    }
}